<?PHP
/*
 * $Id: barge-source.classes.php 139 2009-12-16 15:29:56Z namba0219 $
 * =============================================================================
 * Barge - Web application framework on PHP5 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * =============================================================================
 * For more information, see http://barge.googlecode.com/
 */
namespace barge\source;

use \Exception;
use \DateTime;
use \ArrayIterator;

use barge\System;
use barge\io\File;
use barge\logging\LogFactory;
use barge\utils\ArrayList;
use barge\utils\String;
use barge\utils\StringBuffer;

/**
 * @version 1.0 2009/06/25
 */
class Consntants
{
	const SCOPE_PRIVATE = 'private';
	const SCOPE_PUBLIC = 'public';
	const SCOPE_PROTECTED = 'protected';
}


/**
 * @version 1.0 2009/06/25
 * @see Exception
 */
class SourceParseException extends Exception
{}

/**
 * @version 1.0 2009/06/25
 * @since 1.0 2009/07/04
 */
class SourceParser
{
	/**
	 * @param string $filename
	 * @return SourceEditor
	 * @throws SourceParseException 構文エラーで解析不可の場合にスローされる
	 */
	public static function parse($filename)
	{
		$editor = new SourceEditor();
		$contents = File::getContents($filename);
		$array = preg_split('/(\/\*)|(\*\/)|([ {}\(\)\t\r\n;]+)/', $contents, -1, PREG_SPLIT_DELIM_CAPTURE);
		$iterator = new ArrayIterator($array);
		do {
			$token = $iterator->current();
	echo "[{$token}]\n";
			if (stripos($token, 'class') !== false ||
				stripos($token, 'abstract') !== false ||
				stripos($token, 'final') !== false) {
				 self::parseClass($editor, $iterator);
			}
			if (stripos($token, 'function') !== false) {
				if (self::nextValidToken($iterator)) {
					$editor->addUse($iterator->current());
				} else {
					// Exception
				}
				$funcname = $iterator->current();
				$editor->addFunction($funcname);
			}
			if (stripos($token, 'use') !== false) {
				if (self::nextValidToken($iterator)) {
					$editor->addUse($iterator->current());
				} else {
					// Exception
				}
				//$this->parseUse($iterator);
			}
			if (stripos($token, 'namespace') !== false) {
				if (self::nextValidToken($iterator, true)) {
					$editor->setNamespace($iterator->current());
				} else {
					// Exception
				}
			}
		} while (self::nextValidToken($iterator));
		return $editor;
	}

	/**
	 * @param SourceEditor $editor
	 * @param ArrayIterator $iterator
	 */
	private static function parseClass(SourceEditor $editor, ArrayIterator $iterator)
	{
		$abstract = false;
		$final = false;
		$class = false;
		$baseClass = null;
		$interfaces = new ArrayList();
		$flag = 0;
		do {
			$token = $iterator->current();
			if (stripos($token, 'abstract') !== false) {
				$abstract = true;
			} else if (stripos($token, 'final') !== false) {
				$final = true;
			} else if (stripos($token, 'extends') !== false) {
				if (!self::nextValidToken($iterator)) {
					// Exception:
				}
				$baseClass = $iterator-current();
			} else if (stripos($token, 'implements') !== false) {
				while (self::nextValidToken($iterator) && $iterator->current() !== '{') {
					if ($iterator->current() !== ',') {
						$interfaces->add($iterator->current());
					}
				}
			} else if (stripos($token, '{') !== false) {
				break;
			}
		} while (self::nextValidToken($iterator));
		$name = self::nextValidToken($iterator);
		$classEditor = $editor->addClass($name, $baseClass, $interfaces);
		$log = LogFactory::getLogger();
		$stack = 0;
		do {
			$token = $iterator->current();
			if ($token === '{') {
				$stack++;
			} else if ($token === '}') {
				$stack--;		
			} else if (stripos($token, 'public') !== false ||
				stripos($token, 'protected') !== false || 
				stripos($token, 'private') !== false ||
				stripos($token, 'abstract') !== false ||
				stripos($token, 'function') !== false || 
				stripos($token, 'static') !== false ||
				stripos($token, 'var') !== false) {
				if (!self::nextValidToken($iterator)) {
					// TODO : 
				}
				$nexttoken = $iterator->current();
				if (stripos($nexttoken, '$') === 0) {
					$fieldEditor = $classEditor->addField($nexttoken, $token !== 'static' ? $token : null,
							$token === 'static');
					//$fieldEditor->setInitialValue();
				} else {
				}
			}
			if ($stack === 0) {
				break;
			}
		} while (self::nextValidToken($iterator));
	}
	
	/**
	 * @param ArrayIterator $iterator
	 * @param string $token
	 * @return bool
	 */
	private static function nextToken(ArrayIterator $iterator, $token)
	{
		do {
			if (stripos($iterator->current(), $token) !== false) {
				return true;
			}
		} while (self::nextValidToken($iterator));
		return false;
	}
	
	/**
	 * @param ArrayIterator $iterator
	 * @return bool
	 */
	private static function nextValidToken(ArrayIterator $iterator)
	{
		$iterator->next();
		while ($iterator->valid()) {
			$token = $iterator->current();
			// Block comment.
			if ($token === '/*') {
				$found = false;
				while ($iterator->valid()) {
					$token = $iterator->current();
					if ($token === '*/') {
						$found = true;
						break;
					}
					$iterator->next();
				}
				if (!$found) {
					return false;
				}
			} else if ($token === '//') {
				$found = false;
				while ($iterator->valid()) {
					$token = $iterator->current();
					if ($token === "\n") {
						$found = true;
						break;
					}
					$iterator->next();
				}
				if (!$found) {
					return false;
				}
			} else if (trim($token) !== '') {
				return true;
			}		
			$iterator->next();
		}
		return false;
	}
}

/**
 * @version 1.0 2009/06/25
 * @see Editor
 */
class Doclet extends Editor 
{
	const PARAM = 'param';
	const SEE = 'see';
	const VERSION = 'version';
	const SINCE = 'since';
	const VAR_ = 'var';
	const RETUEN = 'var';
	const AUTHOR = 'author';
	
	/**
	 * @var string
	 */
	private $type;
	
	/**
	 * @var string
	 */
	private $value;
	
	/**
	 * @param Editor $container
	 * @param string $type
	 * @param string $value
	 */
	public function __construct(Editor $container, $type, $value)
	{
		$this->type = $type;
		$this->value = $value;
		parent::__construct($container);
	}
	
	/**
	 * @return string
	 */
	public function getType()
	{
		return $this->type;
	}
	
	/**
	 * @param string $value
	 */
	public function setValue($value)
	{
		$this->value = $value;
	}
		
	/**
	 * @return string
	 */
	public function getValue()
	{
		return $this->value;
	}
	
	/**
	 * @return string
	 * @see Editor#toSource
	 */
	public function toSource()
	{
		return '@' . $this->type . ' '. $this->value;
	}
}

/**
 * @version 1.0 2009/06/25
 * @see Editor
 */
class PHPDoc extends Editor 
{
	/**
	 * @var string
	 */
	private $comment;
	
	/**
	 * @var ArrayList
	 */
	private $doclets;
	
	/**
	 * @param Editor $container
	 */
	public function __construct(Editor $container)
	{
		$this->doclets = new ArrayList();
		parent::__construct($container);
	}
	
	/**
	 * @return ArrayList
	 */
	public function getDoclets()
	{
		return $this->doclets;
	}
	
	/**
	 * @return Doclet
	 */
	public function addDoclet($doclet, $value)
	{
		$doclet = new Doclet($doclet, $value);
		$this->doclets->add($doclet);
		return $doclet;
	}
	
	/**
	 * @return string
	 */
	public function getComment()
	{
		return $this->comment;
	}
	
	/**
	 * @param string $value
	 */
	public function setComment($value)
	{
		$this->comment = $value;
	}
	
	/**
	 * @return string
	 * @see Editor#toSource
	 */
	public function toSource()
	{
		$html = '';
		$html .= "/**\n";
		if ($this->comment != null) {
			$html .= $this->comment . "\n";
		}
		foreach ($this->doclets as $doclet) {	/* @var $doclet Doclet */
			$html .= ' * '.$doclet->toSource()."\n";
		}		
		$html .= " */\n";
		return $html;
	}
}

/**
 * @version 1.0 2009/06/25
 *
 */
abstract class Editor
{
	/**
	 * @var Editor
	 */
	protected $container;
	
	/**
	 * @param Editor $container
	 */
	public function __construct(Editor $container = null)
	{
		$this->container = $container;
	}
	
	/**
	 * @return Editor
	 */
	public function container()
	{
		return $this->container;
	}
	
	/**
	 * @return string
	 */
	abstract public function toSource();
}

/**
 * @version 1.0 2009/06/25
 * @see Editor
 */
class ParameterEditor extends Editor
{
	/**
	 * @var strnig
	 */
	private $name;
	
	/**
	 * @var strnig
	 */
	private $type;
	
	/**
	 * @var mixed
	 */
	private $defaultValue;
	
	/**
	 * @param Editor $container
	 * @param string $name
	 * @param string $type
	 * @param mixed $defaultValue
	 */
	public function __construct($container, $name, $type = null, $defaultValue = null)
	{
		$this->name = $name;
		$this->type = $type;
		$this->defaultValue = $defaultValue;
		parent::__construct($container);
	}
	
	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}
	
	/**
	 * @return string
	 */
	public function getType()
	{
		return $this->type;
	}
	
	/**
	 * @return string
	 */
	public function getDefaultValue()
	{
		return $this->defaultValue;
	}
	
	/**
	 * @return string
	 */
	public function toSource()
	{
		return '';
	}
}

/**
 * @version 1.0 2009/06/25
 * @see Editor
 */
class SourceEditor extends Editor
{
	/**
	 * @var ArrayList
	 */
	private $functions;
	
	/**
	 * @var string
	 */
	private $namespace;
	
	/**
	 * @var ArrayList
	 */
	private $classes;
	
	/**
	 * @var ArrayList
	 */
	private $uses;
	
	public function __construct()
	{
		$this->uses = new ArrayList();
		$this->classes = new ArrayList();
		$this->functions = new ArrayList();
		parent::__construct(null);
	}
	
	/**
	 * @param string $namespace
	 */
	public function setNamespace($namespace)
	{
		$this->namespace = $namespace; 
	}
	
	/**
	 * @return string
	 */
	public function getNamespace()
	{
		return $this->namespace; 
	}
	
	/**
	 * @param string $name
	 * @param string $baseClass
	 * @param ArrayList $interfaceClasses
	 * @return ClassEditor
	 */
	public function addClass($name, $baseClass, ArrayList $interfaceClasses)
	{
		$class = new ClassEditor($this, $name, $baseClass, $interfaceClasses);
		$this->classes->add($class);
		return $class; 
	}
	
	/**
	 * @param string $name
	 * @return FunctionEditor
	 */
	public function addFunction($funcName)
	{
		$function = new FunctionEditor($this, $funcName);
		$this->functions->add($function);
		return $function; 
	}
	
	/**
	 * @param string $className
	 * @return SourceEditor
	 */
	public function addUse($className)
	{
		$this->uses->add($className);
		return $this; 
	}
	
	/**
	 * @return ArrayList
	 */
	public function getUses()
	{
		return $this->uses; 
	}
	
	/**
	 * @return ArrayList
	 */
	public function getFunctions()
	{
		return $this->functions; 
	}
	
	/**
	 * @return string
	 */
	public function toSource()
	{
		return '';
	}
}

/**
 * @version 1.0 2009/06/25
 * @see Editor
 */
class FunctionEditor extends Editor
{
	private $name;
	
	/**
	 * @var ArrayList
	 */
	private $parameters;
	
	/**
	 * @param Editor $container
	 * @param string $name
	 * @param string $baseClass
	 * @param ArrayList $interfaceClasses
	 */
	public function __construct(Editor $container, $name)
	{
		$this->name = $name;
		$this->parameters = new ArrayList();
		parent::__construct($container);
	}
	
	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}
	
	/**
	 * @return ArrayList
	 */
	public function getParameters()
	{
		return $this->parameters;
	}
	
	/**
	 *
	 * @param string $name
	 * @param string $type
	 * @param string $defaultValue
	 * @return ParameterEditor
	 */
	public function addParameter($name, $type = null, $defaultValue = null)
	{
		$parameter = new ParameterEditor($this, $name, $type, $defaultValue);
		$this->parameters->add($parameter);
		return $parameter;
	}
	
	/**
	 * @return string
	 */
	public function toSource()
	{
		return '';
	}
}

/**
 * @version 1.0 2009/06/25
 */
class ClassEditor extends Editor 
{
	private $name;
	private $baseClass;
	
	/**
	 * @var ArrayList
	 */
	private $consts;
	/**
	 * @var ArrayList
	 */
	private $fields;
	/**
	 * @var ArrayList
	 */
	private $interfaceClasses;
	
	/**
	 * @var ArrayList
	 */
	private $methods;
	
	/**
	 * @param Editor $container
	 * @param string $name
	 * @param string $baseClass
	 * @param ArrayList $interfaceClasses
	 */
	public function __construct(Editor $container, $name, $baseClass = null, $interfaceClasses = null)
	{
		$this->name = $name;
		$this->baseClass = $baseClass;
		$this->interfaceClasses = ($interfaceClasses === null ? $interfaceClasses : new ArrayList());
		$this->fields = new ArrayList();
		parent::__construct($container);
	}
	
	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}
	
	/**
	 * @return string
	 */
	public function getBaseClass()
	{
		return $this->baseClass;
	}
	
	/**
	 * @param string $className
	 * @return ClassEditor
	 */
	public function setBaseClass($className)
	{
		$this->baseClass = $className;
		return $this;
	}
	
	/**
	 * @param string $className
	 * @return ClassEditor
	 */
	public function addInterfaceClass($className)
	{
		$this->interfaceClasses->add($className);
		return $this;
	}
	
	/**
	 * @return ClassEditor
	 */
	public function setName($name)
	{
		$this->name = $name;
		return $this;
	}
	
	/**
	 * @return ArrayList
	 */
	public function getMethods()
	{
		return $this->methods;
	}
	
	/**
	 * @return ArrayList
	 */
	public function getFields()
	{
		return $this->fields;
	}
	
	/**
	 * @return ArrayList
	 */
	public function getConsts()
	{
		return $this->consts;
	}
	
	/**
	 * @param string $name
	 * @param mixed $value
	 * @return ConstEditor
	 */
	public function addConst($name, $value)
	{
		$consts = new ConstEditor($name, $value);
		$this->consts->add($consts);
		return $consts;
	}
	
	/**
	 * @param string $name
	 * @param string $scope
	 * @param bool $static
	 * @return FieldEditor
	 */
	public function addField($name, $scope = null, $static = false)
	{
		$field = new FieldEditor($this, $name, $scope, $static);
		$this->fields->add($field);
		return $field;
	}
	
	/**
	 * @param string $name
	 * @param string $scope
	 * @param bool $static
	 * @return MethodEditor
	 */
	public function addMethod($name, $scope = null, $static = false)
	{
		$method = new MethodEditor($this, $name, $scope, $static);
		$this->methods->add($method);
		return $method;
	}
	
	/**
	 * @return string
	 */
	public function toSource()
	{
		return '';
	}
}

/**
 * クラスフィールド
 *
 */
class FieldEditor
{
	/**
	 * @var string
	 */
	private $name;
	
	/**
	 * @var string
	 */
	private $scope;
	
	/**
	 * @var bool
	 */
	private $static;
	
	/**
	 * @var mixed
	 */
	private $initialValue;
	
	/**
	 * @param Editor $container
	 * @param string $name
	 * @param string $scope
	 * @param bool $static
	 */
	public function __construct(Editor $container, $name, $scope = null, $static = false)
	{
		$this->name = $name;
		$this->scope = $scope;
		$this->static = $static;
		parent::__construct($container);
	}

	/**
	 * @mixed string $value
	 */
	public function setInitialValue($value)
	{
		$this->initialValue = $value;
	}
	
	/**
	 * @return string
	 */
	public function toSource()
	{
		return '';
	}
}

/**
 * @version 1.0 2009/06/25
 */
class MethodEditor extends Editor
{
	/**
	 * @var string
	 */
	private $name;
	
	/**
	 * @var string
	 */
	private $scope;
	
	/**
	 * @var bool
	 */
	private $static;
	
	/**
	 * @var ArrayList
	 */
	private $parameters;
	
	/**
	 * @param ClassEditor $container
	 * @param string $name
	 * @param string $scope
	 * @param bool $static
	 */
	public function __construct(ClassEditor $container, $name, $scope = null, $static = false)
	{
		$this->name = $name;
		$this->scope = $scope;
		$this->static = $static;
		$this->parameters = new ArrayList();
		parent::__construct($container);
	}
	
	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * @return string
	 */
	public function getScope()
	{
		return $this->scope;
	}
	
	/**
	 * @return string
	 */
	public function isStatic()
	{
		return $this->static;
	}
	
	/**
	 * @param bool $value
	 * @return MethodEditor
	 */
	public function setStatic($value)
	{
		$this->static = $value;
	}
	
	/**
	 * @return ArrayList
	 */
	public function getParameters()
	{
		return $this->parameters;
	}
	
	/**
	 * @param string $name
	 * @param string $type
	 * @param mixed $defaultValue
	 * @return ParameterEditor
	 */
	public function addParameter($name, $type = null, $defaultValue = null)
	{
		$parameter = new ParameterEditor($this, $name, $type, $defaultValue);
		$this->parameters->add($parameter);
		return $parameter;
	}
	
	/**
	 * @return string
	 */
	public function toSource()
	{
		return '';
	}
}




